############################################################################
#
# Just Another Package Builder http://code.google.com/p/japb
# (c)2011-13 Alexander Galanin
#
# $Id$
#
############################################################################

package require Tcl 8.5

package provide japb 1.1.0

namespace eval japb {

namespace export \
    init \
    registerOption \
    registerPostParseCommand \
    clean \
    dir \
    output \
    isInline \
    call \
    use

variable tempDir .build-tmp
variable buildDir build
variable baseDir {}
variable inline false

set options {
    -builddir   {buildDir   "Build output directory"}
    -tempdir    {tempDir    "Build temporary directory"}
}
set postParseCommands {}

variable subInterpreterResultsCount -1
# options passed to [init] command
variable initOptions {}

# Parse arguments from list and assign values to variables
# Print usage information if requested
# @param args list of keys and values
proc parseOptions {args} {
    variable initOptions $args
    variable options

    foreach {key value} $args {
        if {[dict exists $options $key]} {
            set var [lindex [dict get $options $key] 0]
            set [namespace current]::$var $value
        }
        if {$key eq "-usage"} {
            set err [list "usage: $::argv0 \[options\]"]
            lappend err "Options:"
            dict for {key value} $options {
                set text [lindex [dict get $options $key] 1]
                lappend err "    $key -- $text"
            }
            error [join $err \n] {} USAGE
        }
    }
}

# Add new option to options table used by [parseOptions]
# @param option options name (example: -blahBlah)
# @param var variable name (in ::japb namespace)
# @param description option description
proc registerOption {option var description} {
    variable options
    dict set options $option [list $var $description]
}

# Add new command to be executed after parsing options.
# The command may update internal plugin state based on parsed variable
# values.
# @param cmd Tcl script
proc registerPostParseCommand {cmd} {
    variable postParseCommands
    lappend postParseCommands $cmd
}

# Parse options and run post-parse commands from all plugins
proc init {args} {
    variable buildDir
    variable tempDir
    variable baseDir
    variable postParseCommands
    variable inline

    if {$baseDir eq ""} {
        # TODO: detect path from nameofexe if we are running under starpack
        set baseDir [file dirname [file normalize [info script]]]
    }

    parseOptions {*}$args

    set tempDir [file normalize $tempDir]
    set buildDir [file normalize $buildDir]
    file delete -force $tempDir
    file mkdir $tempDir
    if {!$inline} {
        file mkdir $buildDir
    }

    foreach cmd $postParseCommands {
        uplevel #0 $cmd
    }
}

# Delete temporary directory after build
proc clean {} {
    variable tempDir

    file delete -force $tempDir
}

# Put file nodes into directory
# @param name directory name
# @param args file nodes
# @return file node for directory
proc dir {name args} {
    set dir [filenode mkdir $name]
    foreach fd $args {
        filenode copyToDir $fd $dir
    }
    return $dir
}

# Put all specified file nodes to build directory.
# In inline mode #addSubInterpreterOutput overrides this function.
# @param args file nodes
proc output {args} {
    variable buildDir

    foreach fd $args {
        set outname [file join $buildDir [filenode name $fd]]
        file mkdir [file dirname $outname]
        file delete -force $outname
        file copy [filenode path $fd] $outname
    }
}

# Check that script is executed in inline mode
proc isInline {} {
    variable inline
    return $inline
}

# Call japbfile in inline mode and return map (name -> filenode) of
# generated files
# @param fname file name
# @param args additional parameters
# @return result map
proc call {fname args} {
    variable initOptions
    variable subInterpreterResultsCount
    variable [set resultVar \
        subInterpreterResult[incr subInterpreterResultsCount] \
    ]
    variable tempDir

    set interp [interp create]
    $interp eval [list set auto_path $::auto_path]
    $interp eval {
        package require japb
        package require japb::filenode
        # set inline mode flag
        set ::japb::inline true
    }
    # set arguments array
    $interp eval [list set ::argv0 $fname]
    $interp eval [list set ::argv [concat \
        $initOptions \
        $args \
    ]]
    # set temporary directory to subdir of current temp dir
    $interp eval [list set ::japb::tempDir [file join $tempDir sub-script]]
    # link 'output' command in subinterpreter with command in caller
    # namespace
    $interp alias ::japb::output \
        [namespace code [list addSubInterpreterOutput $resultVar]]
    # import japb to subinterpreter
    $interp eval {
            namespace import ::japb::*
    }
    # invoke script file in a subnamespace
    set pwd [pwd]
    $interp eval [list apply {{scriptName} {
            cd [file dirname $scriptName]
            namespace eval :: [list source [file tail $scriptName]]
        }} $fname \
    ]
    cd $pwd
    # delete interpreter
    interp delete $interp

    set result [set $resultVar]
    unset $resultVar
    return $result
}

# This proc overrides #output in inline mode.
# Copy generated file node from subinterpreter build dir to caller build dir
# and fill list of output file nodes.
# @param resultVar name of variable in current namespace that used for
# keeping subscript execution result
# @param args key1 filenode1 ??key2 filenode2? ...? - list of keys and filenodes
# @return dictionary key-filenode
proc addSubInterpreterOutput {resultVar args} {
    variable $resultVar
    foreach {key node} $args {
        set newnode [filenode mkfile [filenode name $node]]
        file copy [filenode path $node] [filenode path $newnode]
        dict set $resultVar $key $newnode
    }
}

namespace eval use {

# Load specified JAPB extension(s)
# @param args extensions list
proc extension {args} {
    foreach ext $args {
        package require japb::$ext
    }
    # re-import commands
    uplevel {namespace import ::japb::*}
}

namespace export \
    extension
namespace ensemble create
}

}

